﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.IO;
using System.Windows.Media.Imaging;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;

namespace TwoPageView3 {
    class TiledImageItem : IVirtualTile {
        private static Size msDefaultTileSize;
        private static Size msDefaultTileSizeDevicePixel;
        private Image mImage;
        private TpvFileInfo mFileInfo;
        private CancellationTokenSource mCtsForDispose;
        private CancellationToken mTokenForWindowClose;
        private Object mLock = new Object();
        private static ThumbnailCache mThumbnailCache;
        private Size mBitmapSizeDip;
        private IVirtualTileEvents mCallback;
        private readonly int mOrdinalOnDirectory;

        public static void ConnectTo(ThumbnailCache tc) {
            mThumbnailCache = tc;
        }

        public static void UpdateDefaultTileSize(Size size, Size sizeDevicePixel) {
            msDefaultTileSize = size;
            msDefaultTileSizeDevicePixel = sizeDevicePixel;
        }

        public TiledImageItem(TpvFileInfo fileInfo, IVirtualTileEvents callback, CancellationToken token) {
            mFileInfo            = fileInfo;
            mTokenForWindowClose = token;
            mCallback            = callback;
            mBitmapSizeDip       = Size.Empty;
            mOrdinalOnDirectory = fileInfo.OrdinalOnDirectory;
        }

        public UIElement Visual {
            get {
                return mImage;
            }
        }

        public int OrdinalOnDirectory { get { return mOrdinalOnDirectory; } }

        Size IVirtualTile.DesirableSize() {
            return mBitmapSizeDip;
        }

        /// <summary>
        /// Must be called from the UI thread
        /// </summary>
         void IVirtualTile.Resize(Size newSize) {
            if (mImage != null) {
                mImage.Width = newSize.Width;
                mImage.Height = newSize.Height;
            }
        }

        /// <summary>
        /// Dispose the WPF visual for this object.
        /// </summary>
         void IVirtualTile.DisposeVisual() {
            lock (mLock) {
                if (mCtsForDispose != null) {
                    mCtsForDispose.Cancel();
                }
                mImage = null;
            }

            mCallback.OnVitualTileVisualDispose(this);
            //Console.WriteLine("DisposeVisual {0}", mFileInfo.DisplayPath);
        }

        /// <summary>
        /// Create the WPF visual for this object.
        /// </summary>
        /// <param name="parent">The canvas that is calling this method</param>
        /// <returns>The visual that can be displayed</returns>
         UIElement IVirtualTile.CreateVisual(VirtualizingTilePanel parent) {
            lock (mLock) {
                if (null != mImage) {
                    return mImage;
                }

                mCtsForDispose = new CancellationTokenSource();

                mImage = new Image();
                mImage.Stretch = Stretch.Uniform;
                mImage.Width = msDefaultTileSize.Width;
                mImage.Height = msDefaultTileSize.Height;
                mImage.Tag = this;
            }

            var uiThreadDispatcher = Dispatcher.CurrentDispatcher;
            var tokenForDispose = mCtsForDispose.Token;

            Task.Factory.StartNew(new Action(() => {
                if (tokenForDispose.IsCancellationRequested) {
                    //Console.WriteLine("  Canceled 1 {0}", mFileInfo.DisplayPath);
                    return;
                }
                BitmapImage bi = null;
                BitmapLoadFromFileOrCache(out bi);

                if (tokenForDispose.IsCancellationRequested) {
                    //Console.WriteLine("  Canceled 3 {0}", mFileInfo.DisplayPath);
                    return;
                }

                try {
                    if (bi == null) {
                        return;
                    }
                    uiThreadDispatcher.Invoke(new Action(() => {
                        lock (mLock) {
                            if (mImage == null) {
                                return;
                            }
                            mImage.Source = bi;
                            mBitmapSizeDip = Utility.DevicePixelToSize(mImage, new Size(bi.PixelWidth, bi.PixelHeight));
                            mCallback.OnVitualTileVisualShow(this);
                            //Console.WriteLine("  Set element {0}", mFileInfo.DisplayPath);
                        }
                    }), DispatcherPriority.Render, mTokenForWindowClose);
                } catch (TaskCanceledException ex) {
                    Console.WriteLine("CreateVisual canceled for program exit {0}", ex);
                }
            }), mTokenForWindowClose, TaskCreationOptions.LongRunning, PriorityScheduler.BelowNormal);

            //Console.WriteLine("CreateVisual {0}", mFileInfo.DisplayPath);
            return mImage;
        }

        private bool BitmapLoadFromFileOrCache(out BitmapImage bi) {
            var entry = mThumbnailCache.Find(mFileInfo.DisplayPath, mFileInfo.LastWriteTimeUtc, mFileInfo.Length);
            if (null != entry) {
                bi = entry.ThumbnailBitmap;
                return true;
            }
            if (!CreateThumbnailBitmapImage(mFileInfo.ActualPath, out bi)) {
                return false;
            }

            if (bi != null) {
                mThumbnailCache.Add(mFileInfo.DisplayPath,
                        new ThumbnailCacheEntry(mFileInfo.LastWriteTimeUtc, mFileInfo.Length, bi, new Size(bi.PixelWidth, bi.PixelHeight)));
            }

            return bi != null;
        }

        private bool CreateThumbnailBitmapImage(string path, out BitmapImage bi) {
            byte[] fileContent = null;
            using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read)) {
                using (var br = new BinaryReader(fs)) {
                    //Console.WriteLine("{0}", fs.Length);
                    fileContent = br.ReadBytes((int)fs.Length);
                }
            }

            try {
                using (var ms = new MemoryStream(fileContent)) {
                    bi = new BitmapImage();
                    bi.BeginInit();
                    bi.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
                    bi.CacheOption = BitmapCacheOption.OnLoad;
                    bi.DecodePixelHeight = (int)msDefaultTileSizeDevicePixel.Height;
                    bi.StreamSource = ms;
                    bi.EndInit();
                    bi.Freeze();
                }

                if (msDefaultTileSizeDevicePixel.Width < bi.PixelWidth) {
                    using (var ms = new MemoryStream(fileContent)) {
                        bi = new BitmapImage();
                        bi.BeginInit();
                        bi.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
                        bi.CacheOption = BitmapCacheOption.OnLoad;
                        bi.DecodePixelWidth = (int)msDefaultTileSizeDevicePixel.Width;
                        bi.StreamSource = ms;
                        bi.EndInit();
                        bi.Freeze();
                    }
                }
            } catch (NotSupportedException ex) {
                Console.WriteLine("{0}", ex);
                bi = null;
            } catch (FileFormatException ex) {
                Console.WriteLine("{0}", ex);
                bi = null;
            }
            return true;
        }
    }
}
